package iot_aliyun

import (
	"bytes"
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"fmt"
	"hash"
	"sort"
	"strings"
)

// MQTTDeviceConn mqtt连接信息
type MQTTDeviceConn struct {
	//clientId和username所有请求必须携带，后面可以判断当前连接身份再处理
	MqttClientId string `json:"clientid"`
	UserName     string `json:"username"`

	//password只在mqtt连接后授权检查需要
	Password string `json:"password"`
}

// Base 基本信息,从clientId和userName解析得到相关信息
func (conn *MQTTDeviceConn) Base() ConnDeviceInfo {
	var info ConnDeviceInfo
	info.ClientId, info.Params = conn.parsClientId(conn.MqttClientId)

	switch info.SecureMode() {
	case SecureByDevice:
		fallthrough
	case SecureByDeviceTls:
		info.ProductKey, info.DeviceKey = conn.parseUserName()
	case SecureByProduct:
		info.ProductKey, info.DeviceKey = conn.parseUserName()
	case SecureByStation:
		info.StationKey, info.DeviceKey = conn.parseUserName()
	}

	return info
}

// Auth 授权验证
func (conn *MQTTDeviceConn) Auth(secretFunc func(ConnDeviceInfo) string) (ConnDeviceInfo, bool) {
	if secretFunc == nil {
		secretFunc = func(info ConnDeviceInfo) string {
			return ""
		}
	}
	info := conn.Base()

	switch info.SecureMode() {
	case SecureByDevice:
		fallthrough
	case SecureByDeviceTls:
		info.DeviceSecret = secretFunc(info)
	case SecureByProduct:
		info.ProductSecret = secretFunc(info)
	case SecureByStation:
		info.StationSecret = secretFunc(info)
	}
	//fmt.Println("xxxx=", conn.Password, info.Password())

	return info, info.Password() == conn.Password && conn.Password != ""
}

// parseUserName username={deviceId}/{productId}
func (conn *MQTTDeviceConn) parseUserName() (productId, deviceId string) {
	arr := strings.Split(conn.UserName, "&")
	if len(arr) != 2 {
		return "", ""
	}
	return arr[1], arr[0]
}

// parseUserName username={deviceId}/{productId}
func (conn *MQTTDeviceConn) parsClientId(mqttClientId string) (clientId string, params map[string]string) {
	params = map[string]string{}
	arr := strings.Split(mqttClientId, "|")
	if len(arr) > 0 {
		clientId = arr[0]
	}
	if len(arr) > 1 {
		tmp := strings.Split(arr[1], ",")
		for _, v := range tmp {
			if vArr := strings.Split(v, "="); len(vArr) > 1 {
				params[vArr[0]] = vArr[1]
			} else if len(vArr) > 0 {
				params[vArr[0]] = ""
			}
		}
	}

	return clientId, params
}

type ConnDeviceInfo struct {
	ClientId      string `json:"client_id"`
	ProductKey    string `json:"product_key"`
	ProductSecret string `json:"product_secret"`
	DeviceKey     string `json:"device_key"`
	DeviceSecret  string `json:"device_secret"`
	StationKey    string `json:"station_key"`
	StationSecret string `json:"station_secret"`

	//在mqttClientId中后面带的参数集合
	//支持参数：
	//	signmethod
	//	securemode
	//	timestamp
	Params map[string]string `json:"params"`
}

func (c *ConnDeviceInfo) SecureMode() SecureMode {
	if c.Params == nil {
		return ""
	}
	return SecureMode(c.Params["securemode"])
}

func (c *ConnDeviceInfo) SignMethod() string {
	return c.Params["signmethod"]
}

func (c *ConnDeviceInfo) Timestamp() string {
	return c.Params["timestamp"]
}

// SetParam 设置参数
// secure，安全模式
// timestamp，毫秒级时间戳
// signMethod，加密方法：hmacmd5、hmacsha1、hmacsha256
func (c *ConnDeviceInfo) SetParam(secure SecureMode, timestamp int64, signMethod string) {
	if c.Params == nil {
		c.Params = map[string]string{}
	}
	c.Params["securemode"] = string(secure)
	c.Params["timestamp"] = fmt.Sprint(timestamp)
	c.Params["signmethod"] = signMethod
}

type SecureMode string

// 目前支持的安全认证方式
// 阿里云文档 https://help.aliyun.com/zh/iot/user-guide/establish-mqtt-connections-over-tcp?spm=a2c4g.11186623.0.0.3104811c1RugKx
const (
	SecureByDeviceTls SecureMode = "2"    //一型一密预注册认证-TLS直连（与下面的SecureMode=3基本一致，无区别）
	SecureByDevice    SecureMode = "3"    //一型一密预注册认证-TCP直连
	SecureByProduct   SecureMode = "-2"   //一型一密免预注册（即产品认证方式，动态注册设备）
	SecureByStation   SecureMode = "-102" //免预注册（站点认证方式，动态注册设备）,本地iot特有
)

func (c *ConnDeviceInfo) Password() string {
	var timestamp string
	if tmp, exist := c.Params["timestamp"]; exist {
		timestamp = tmp
	}

	data := map[string]string{}
	var secret string

	switch c.SecureMode() {
	case SecureByDevice:
		fallthrough
	case SecureByDeviceTls:
		data = map[string]string{
			"productKey": c.ProductKey,
			"deviceName": c.DeviceKey,
			"timestamp":  timestamp,
			"clientId":   c.ClientId,
		}
		secret = c.DeviceSecret
	case SecureByProduct:
		data = map[string]string{
			"productKey": c.ProductKey,
			"deviceName": c.DeviceKey,
			"timestamp":  timestamp,
			"clientId":   c.ClientId,
		}
		secret = c.ProductSecret
	case SecureByStation:
		data = map[string]string{
			"stationKey": c.StationKey,
			"deviceName": c.DeviceKey,
			"timestamp":  timestamp,
			"clientId":   c.ClientId,
		}
		secret = c.StationSecret
	}

	if secret == "" {
		return ""
	}
	s := mapSortString(data)
	var mac hash.Hash = hmac.New(md5.New, []byte(secret)) //默认MD5

	if tmp, exist := c.Params["signmethod"]; exist {
		switch tmp {
		case "hmacmd5":
			mac = hmac.New(md5.New, []byte(secret))
		case "hmacsha1":
			mac = hmac.New(sha1.New, []byte(secret))
		case "hmacsha256":
			mac = hmac.New(sha256.New, []byte(secret))
		}
	}
	if mac == nil {
		return ""
	}
	mac.Write([]byte(s))
	return fmt.Sprintf("%02x", mac.Sum(nil))
}

// mapSortString
func mapSortString(params map[string]string) string {
	var arr []string
	for k, v := range params {
		arr = append(arr, k+v)
	}
	sort.Slice(arr, func(i, j int) bool {
		return arr[i] < arr[j]
	})
	return strings.Join(arr, "")
}

func CalculateSign(clientId, productKey, deviceName, deviceSecret, timeStamp string) MQTTDeviceConn {
	var raw_passwd bytes.Buffer
	raw_passwd.WriteString("clientId" + clientId)
	raw_passwd.WriteString("deviceName")
	raw_passwd.WriteString(deviceName)
	raw_passwd.WriteString("productKey")
	raw_passwd.WriteString(productKey)
	raw_passwd.WriteString("timestamp")
	raw_passwd.WriteString(timeStamp)

	// hmac, use sha1
	mac := hmac.New(sha1.New, []byte(deviceSecret))
	mac.Write([]byte(raw_passwd.String()))
	password := fmt.Sprintf("%02x", mac.Sum(nil))
	username := deviceName + "&" + productKey

	var MQTTClientId bytes.Buffer
	MQTTClientId.WriteString(clientId)
	// hmac, use sha1; securemode=2 means TLS connection
	MQTTClientId.WriteString("|securemode=2,_v=paho-go-1.0.0,signmethod=hmacsha1,timestamp=")
	MQTTClientId.WriteString(timeStamp)
	MQTTClientId.WriteString("|")

	auth := MQTTDeviceConn{Password: password, UserName: username, MqttClientId: MQTTClientId.String()}
	return auth
}

func CalcMQTTConn(info ConnDeviceInfo) MQTTDeviceConn {
	conn := MQTTDeviceConn{}
	conn.MqttClientId = fmt.Sprintf("%s|securemode=%s,_v=paho-go-1.0.0,signmethod=%s,timestamp=%s|", info.ClientId, info.SecureMode(), info.SignMethod(), info.Timestamp())

	switch info.SecureMode() {
	case SecureByDevice:
		fallthrough
	case SecureByDeviceTls:
		conn.UserName = info.DeviceKey + "&" + info.ProductKey
	case SecureByProduct:
		conn.UserName = info.DeviceKey + "&" + info.ProductKey
	case SecureByStation:
		conn.UserName = info.DeviceKey + "&" + info.StationKey
	}
	conn.Password = info.Password()

	return conn
}
